#include "t_box_player_ctx.h"
//#include "t_box_context.h"
#include "t_box_video.h"

#define USE_AUDIO_THREAD 0

namespace BoxPlayer //[class T_Box_Context_Content]
{
    class T_Box_Context_Content : public T_Box_Context
    {
    public:
        explicit T_Box_Context_Content(T_Box_Core *a_core)
            : T_Box_Context(a_core)
        {
            this->openAudio();
            this->openVideo();
            this->core()->seekManager()->addSeekable(this);
        }
    protected:
        void run();
        bool handleSeek();
    public:
        bool needBuffering();
    };
};

namespace BoxPlayer //[class T_Box_Player_Context]
{
    class T_Box_Player_ContextPrivate : public QObject
    {
    protected:
        Q_DECLARE_PUBLIC(T_Box_Player_Context)
        T_Box_Player_Context *q_ptr;
    public:
        T_Box_Context_Content work;
        //T_Box_Context video1;
        T_Box_Video_Multithread_Manager m_video_mgr;
    public:
        explicit T_Box_Player_ContextPrivate(T_Box_Player_Context *me)
            : QObject(me), q_ptr(me)
            , work(me->m_core)
            //, video1(me->m_core)
            , m_video_mgr(me->m_core)
        {
        }
        virtual ~T_Box_Player_ContextPrivate()
        {
        }
    };

    T_Box_Player_Context::T_Box_Player_Context(T_Box_Core *a_core)
        : m_core(a_core)
        , m_speaker(NULL)
        , m_decode_thread(0)
        , m_audio_thread(0)
    {
        d_ptr = new T_Box_Player_ContextPrivate(this);
        Q_D(T_Box_Player_Context);
        Q_ASSERT(m_core);
        Q_ASSERT(m_core->isValid());
        Q_ASSERT(m_core->globalControl()->isValid());
        Q_ASSERT(m_core->globalControl()->formatContext());
        Q_ASSERT(d->work.video()->isOpen());
        Q_ASSERT(d->work.audio()->isOpen());
        d->work.start();

        //d->video1.openVideo();
        //m_core->seekManager()->addSeekable(&d->video1);
#if !USE_AUDIO_THREAD
        m_speaker = new T_Box_AudioOutput(&d->work, 0);
        m_speaker->start();
#endif
    }

    T_Box_Player_Context::~T_Box_Player_Context()
    {
        Q_D(T_Box_Player_Context);
        qDebug() << "[~T_Box_Player_Context(1)]";
        m_speaker->stop();
        delete m_speaker;
        qDebug() << "[~T_Box_Player_Context(2)]";
        d->work.finish();
        qDebug() << "[~T_Box_Player_Context(3)]";
    }

    void T_Box_Player_Context::start()
    {
#if USE_DECODE_THREAD
        Q_ASSERT(!m_decode_thread);
        m_decode_thread = new T_Box_Player_Decode_Thread(this->video());
        m_decode_thread->start();
#endif
#if USE_AUDIO_THREAD
        Q_ASSERT(!m_audio_thread);
        m_audio_thread = new T_Box_Player_Audio_Thread(this);
        m_audio_thread->start();
#endif
    }
    void T_Box_Player_Context::finish()
    {
        qDebug() << "[T_Box_Player_Context::finish(1)]";
        if(m_decode_thread) m_decode_thread->finish();
        qDebug() << "[T_Box_Player_Context::finish(2)]";
        if(m_audio_thread) m_audio_thread->finish();
        qDebug() << "[T_Box_Player_Context::finish(3)]";
    }

    T_Box_Media *T_Box_Player_Context::audio()
    {
        Q_D(T_Box_Player_Context);
        return d->work.audio();
    }

    T_Box_Media *T_Box_Player_Context::video()
    {
        Q_D(T_Box_Player_Context);
        return d->work.video();
    }

    T_SharedPointerQueue<T_AV_Packet> *T_Box_Player_Context::videoPacketQueue()
    {
        Q_D(T_Box_Player_Context);
        return d->m_video_mgr.packetQueue();
    }

    bool T_Box_Player_Context::needBuffering()
    {
        Q_D(T_Box_Player_Context);
        QSharedPointer<T_AV_Packet> v_head = this->videoPacketQueue()->head();
        if(v_head && v_head->frameNo()<=T_Box::PLAYER_AHEAD_IMAGE && v_head->isDecoded())
        {
            //return false;
        }
        else
        {
            static bool v_emergency = false;
            QSharedPointer<T_AV_Packet> v_head = this->videoPacketQueue()->head();
            if(v_head && v_head->frameNo()==0)
            {
                v_emergency = false;
            }
            if(v_emergency)
            {
                if(this->videoPacketQueue()->size()<T_Box::PLAYER_IMAGE_STOCK)
                {
                    return true;
                }
            }
            v_emergency = false;
#if 0x1
            if(v_head && v_head->frameNo()==0)
            {
                return false; //display frame 0
            }
            if(this->videoPacketQueue()->endlessCount()<=T_Box::PLAYER_AHEAD_IMAGE)
            {
                return false;
            }
#endif
            if(this->videoPacketQueue()->size()<T_Box::PLAYER_AHEAD_IMAGE)
            {
                v_emergency = true;
                return true;
            }
        }
        return d->work.needBuffering();
    }

    T_Box_AudioOutput *T_Box_Player_Context::speaker()
    {
        return m_speaker;
    }

    void T_Box_Context_Content::run()
    {
        this->ready() = true;
        T_Box_Seek_Manager *v_seek_manager = this->core()->seekManager();
        for(;;)
        {
            if(this->closing())
            {
                break;
            }
            if(this->handleSeek())
            {
                continue;
            }
            QSharedPointer<T_AV_Packet> v_av_packet = this->enqueuePacket();
            if(!v_av_packet)
            {
                v_seek_manager->wait(1);
            }
        }
    }

    bool T_Box_Context_Content::handleSeek()
    {
        T_Box_Seek_Manager *v_seek_manager = this->core()->seekManager();
        if(v_seek_manager->pendingRequest() && v_seek_manager->elapsed()>100)
        {
            int v_seek_target = v_seek_manager->dequeueRequest();
            v_seek_manager->seek(v_seek_target);
        }
        if(this->needBuffering())
        {
            return false; //must read next packet.
        }
        else
        {
            if(v_seek_manager->isCriticalTime())
            {
                // may become needBuffering() soon; so looping is needed.
                // wait() exit immediately on seek request
                v_seek_manager->wait(1);
                return true; //request looping
            }
            else if(video()->packetQueue()->maxTiming()>=v_seek_manager->seekableDuration())
            {
                v_seek_manager->wait(1);
                return true; //request looping
            }
            else
            {
                return false; //may read next packet.
            }
        }
    }

    bool T_Box_Context_Content::needBuffering()
    {
#if USE_DECODE_THREAD
        QSharedPointer<T_AV_Packet> v_head = this->video()->packetQueue()->head();
        if(v_head && v_head->frameNo()<=T_Box::PLAYER_AHEAD_IMAGE && v_head->isDecoded())
        {
            //return false;
        }
        else
        {
            static bool v_emergency = false;
            if(v_emergency)
            {
                if(this->video()->packetQueue()->decodedCount(T_Box::PLAYER_IMAGE_STOCK)<T_Box::PLAYER_IMAGE_STOCK)
                {
                    return true;
                }
            }
            v_emergency = false;
            if(this->video()->packetQueue()->decodedCount(T_Box::PLAYER_AHEAD_IMAGE)<T_Box::PLAYER_AHEAD_IMAGE)
            {
                v_emergency = true;
                return true;
            }
        }
#endif
        if(this->video()->packetQueue()->endlessDuration(this->core()->timeline()->elapsed())<T_Box::PLAYER_BUFFER_TIME)
        {
            return true;
        }
        return false;
    }
};

namespace BoxPlayer //[class T_Box_Player_Decode_Thread]
{
    void T_Box_Player_Decode_Thread::run()
    {
        this->ready() = true;
        if(false)
        {
            return;
        }
        T_Box_Seek_Manager *v_seek_manager = m_video->core()->seekManager();
        T_AV_Packet_Queue *v_video_qu = m_video->packetQueue();
        for(;;)
        {
            if(this->closing())
            {
                break;
            }
            if(v_video_qu->decodedCount(T_Box::PLAYER_ENOUGH_IMAGE)>=T_Box::PLAYER_ENOUGH_IMAGE)
            {
                msleep(1);
            }
            {
                T_READ_LOCKER(v_seek_lock, v_seek_manager)
                        int v_decode_count = 0;
                for(int i=0; i<v_video_qu->size(); i++)
                {
                    if(v_decode_count>=T_Box::PLAYER_IMAGE_STOCK)
                    {
                        break;
                    }
                    QSharedPointer<T_AV_Packet> v_packet = v_video_qu->value(i);
                    if(v_packet==NULL) break;
                    if(!v_packet->isDecoded())
                    {
                        v_packet->decodeVideoToNull(true);
                        v_decode_count++;
                        break;
                    }
                    v_decode_count++;
                }
            }
        }
    }
};


namespace BoxPlayer //[class T_Box_Player_Audio_Thread]
{
    void T_Box_Player_Audio_Thread::run()
    {
        m_player_ctx->m_speaker = new T_Box_AudioOutput(&m_player_ctx->d_func()->work, 0);
        m_player_ctx->m_speaker->start();
        this->ready() = true;
        exec();
    }
};

#if 0x0
//http://blog.csdn.net/huyinguo/archive/2009/10/25/4725326.aspx
//needs ffmpeg-mt
static void validate_thread_parameters(AVCodecContext *avctx)
{
    int frame_threading_supported = (avctx->codec->capabilities & CODEC_CAP_FRAME_THREADS)
                                    && !(avctx->flags & CODEC_FLAG_TRUNCATED)
                                    && !(avctx->flags & CODEC_FLAG_LOW_DELAY)
                                    && !(avctx->flags2 & CODEC_FLAG2_CHUNKS);
    if (avctx->thread_count <= 1)
        avctx->active_thread_type = 0;
    else if (frame_threading_supported && (avctx->thread_type & FF_THREAD_FRAME))
        avctx->active_thread_type = FF_THREAD_FRAME;
    else
        avctx->active_thread_type = FF_THREAD_SLICE;
}
#endif
